<!DOCTYPE html>
<html lang="zh">
    <head>
        <meta charset="utf-8" />
        <title>LLM Chat (Flask + Ollama)</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />

        <!-- Semantic UI -->
        <link
            rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/semantic-ui@2.5.0/dist/semantic.min.css"
        />
        <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.5.0/dist/semantic.min.js"></script>
    </head>
    <body class="ui container" style="margin-top: 2em">
        <h2 class="ui header">🤖 LLM Chat</h2>

        <!-- 工具栏 -->
        <form class="ui form">
            <div class="fields">
                <div class="four wide field">
                    <label>模型</label>
                    <input id="model" value="qwen2.5:3b" />
                </div>
                <div class="three wide field">
                    <label>温度</label>
                    <input
                        id="temperature"
                        type="number"
                        step="0.1"
                        value="0.7"
                    />
                </div>
                <div class="five wide field">
                    <label>API Key</label>
                    <input id="apiKey" placeholder="API-Key（如启用）" />
                </div>
                <div class="four wide field">
                    <label>模式</label>
                    <div class="inline fields">
                        <div class="field">
                            <div class="ui radio checkbox">
                                <input
                                    type="radio"
                                    name="mode"
                                    value="stream"
                                    checked
                                />
                                <label>流式</label>
                            </div>
                        </div>
                        <div class="field">
                            <div class="ui radio checkbox">
                                <input
                                    type="radio"
                                    name="mode"
                                    value="normal"
                                />
                                <label>非流式</label>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </form>

        <!-- 模型状态 -->
        <div class="ui segment">
            <button id="checkHealth" class="ui green button mini">
                检查服务状态
            </button>
            <span id="healthStatus" class="ui text"></span>
            <button id="loadModels" class="ui blue button mini">
                列出模型
            </button>
        </div>
        <div id="modelsList" class="ui list"></div>

        <!-- 消息区 -->
        <div
            id="messages"
            class="ui segment"
            style="height: 60vh; overflow-y: auto"
        ></div>

        <!-- 输入区 -->
        <div class="ui form">
            <div class="field">
                <textarea
                    id="prompt"
                    rows="2"
                    placeholder="请输入问题..."
                ></textarea>
            </div>
            <div class="ui buttons">
                <button id="send" class="ui primary button">发送</button>
                <div class="or"></div>
                <button id="clear" class="ui red button">清空</button>
            </div>
        </div>

        <!-- Axios -->
        <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
        <script>
            const messagesEl = document.getElementById('messages');
            const promptEl = document.getElementById('prompt');
            const sendBtn = document.getElementById('send');
            const clearBtn = document.getElementById('clear');
            const modelEl = document.getElementById('model');
            const tempEl = document.getElementById('temperature');
            const keyEl = document.getElementById('apiKey');
            const healthBtn = document.getElementById('checkHealth');
            const healthStatus = document.getElementById('healthStatus');
            const loadModelsBtn = document.getElementById('loadModels');
            const modelsList = document.getElementById('modelsList');

            let history = [];

            function append(role, text) {
                const div = document.createElement('div');
                div.className =
                    role === 'user'
                        ? 'ui right aligned message'
                        : 'ui left aligned message';
                div.innerHTML = `
        <div class="header">${role}</div>
        <p>${text}</p>`;
                messagesEl.appendChild(div);
                messagesEl.scrollTop = messagesEl.scrollHeight;
                return div;
            }

            function toMessages() {
                return history.map(h => ({ role: h.role, content: h.content }));
            }

            async function chat(prompt, mode) {
                const model = modelEl.value.trim();
                const temperature = parseFloat(tempEl.value || '0.7');
                const headers = { 'Content-Type': 'application/json' };
                const apiKey = keyEl.value.trim();
                if (apiKey) headers['API-Key'] = apiKey;

                history.push({ role: 'user', content: prompt });
                append('user', prompt);

                const assistantNode = append('assistant', '');

                if (mode === 'normal') {
                    try {
                        const res = await axios.post(
                            '/api/chat',
                            {
                                model,
                                messages: toMessages(),
                                options: { temperature },
                            },
                            { headers }
                        );
                        const text =
                            res.data.message?.content ??
                            res.data.response ??
                            JSON.stringify(res.data);
                        assistantNode.querySelector('p').innerText = text;
                        history.push({ role: 'assistant', content: text });
                    } catch (err) {
                        assistantNode.querySelector('p').innerText = '请求失败';
                    }
                } else {
                    const sseRes = await fetch('/api/chat/stream', {
                        method: 'POST',
                        headers,
                        body: JSON.stringify({
                            model,
                            messages: toMessages(),
                            options: { temperature },
                        }),
                    });
                    const reader = sseRes.body.getReader();
                    const decoder = new TextDecoder('utf-8');
                    let assistantText = '';
                    while (true) {
                        const { value, done } = await reader.read();
                        if (done) break;
                        const chunk = decoder.decode(value, { stream: true });
                        const lines = chunk.split('\n');
                        for (const line of lines) {
                            if (!line.startsWith('data:')) continue;
                            const jsonStr = line.slice(5).trim();
                            if (!jsonStr) continue;
                            try {
                                const obj = JSON.parse(jsonStr);
                                const delta =
                                    obj.message?.content ?? obj.response ?? '';
                                assistantText += delta;
                                assistantNode.querySelector('p').innerText =
                                    assistantText;
                            } catch (e) {}
                        }
                    }
                    history.push({ role: 'assistant', content: assistantText });
                }
            }

            sendBtn.addEventListener('click', () => {
                const text = promptEl.value.trim();
                if (!text) return;
                promptEl.value = '';
                const mode = document.querySelector(
                    'input[name="mode"]:checked'
                ).value;
                chat(text, mode);
            });

            clearBtn.addEventListener('click', () => {
                history = [];
                messagesEl.innerHTML = '';
            });

            promptEl.addEventListener('keydown', e => {
                if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
                    sendBtn.click();
                }
            });

            // 健康检查
            healthBtn.addEventListener('click', async () => {
                try {
                    const res = await axios.get('/healthz');
                    healthStatus.textContent = res.data.ok
                        ? '✅ 服务正常'
                        : '❌ 服务异常';
                } catch {
                    healthStatus.textContent = '❌ 请求失败';
                }
            });

            // 列出模型
            loadModelsBtn.addEventListener('click', async () => {
                modelsList.innerHTML = '';
                try {
                    const res = await axios.get('/api/models');
                    if (res.data.models) {
                        res.data.models.forEach(m => {
                            const item = document.createElement('div');
                            item.className = 'item';
                            item.textContent = m.name;
                            modelsList.appendChild(item);
                        });
                    } else {
                        modelsList.innerHTML =
                            '<div class="item">无模型数据</div>';
                    }
                } catch {
                    modelsList.innerHTML = '<div class="item">请求失败</div>';
                }
            });
        </script>
    </body>
</html>
